Synthesize Langfuse per-branch dispatch spans#190
Merged
chris-colinsky merged 1 commit intoJun 25, 2026
Conversation
The Langfuse observer did not synthesize the per-branch dispatch-span observation that observability 4.3 / 8.4.2 + proposal 0044 mandate, so a parallel_branches trace was two-level (dispatcher -> inner) instead of three (dispatcher -> per-branch span -> inner). The OTel observer has this; the Langfuse side shipped without it (0044 was OTel-only in v0.11.0). Port the synthesis: cache the declared branch_names per pb node, open a per-branch Span observation (once per branch) from the first inner branch event, reparent the inner observations under it, and close on the pb node's completed event. The per-branch observation carries the OA-emitted branch_name plus the caller baseline and any per-branch augmentation; the Generation observation now carries branch_name (and fan_out_index) too. Un-defer conformance fixture 030.
There was a problem hiding this comment.
Pull request overview
This PR updates the Langfuse observer’s span hierarchy for parallel_branches to synthesize a per-branch dispatch-span, aligning the Langfuse trace shape with the spec requirements and the existing OTel observer behavior. It also promotes the previously deferred conformance fixture 030 to run under the Langfuse harness and adds a unit test to ensure the per-branch span synthesis is idempotent.
Changes:
- Add per-branch dispatch-span synthesis for Langfuse
parallel_branchesso branch inner-node spans nest under a branch span (3-level tree). - Enable conformance fixture
030-caller-metadata-parallel-branches-per-branchfor Langfuse by moving it into the Langfuse harness fixture set and updating deferral commentary. - Add a unit regression test asserting exactly one per-branch observation is created for a subgraph branch with multiple inner nodes.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
src/openarmature/observability/langfuse/observer.py |
Adds per-branch dispatch-span caching/synthesis and re-parents inner branch nodes under the branch span; extends Generation metadata with branch_name/fan_out_index. |
tests/unit/test_observability_langfuse.py |
Adds a regression test ensuring per-branch dispatch synthesis is idempotent for subgraph branches. |
tests/conformance/test_observability.py |
Moves fixture 030 into the Langfuse harness fixture set and removes the old unit-only deferral entry. |
tests/conformance/test_observability_langfuse.py |
Un-defers fixture 030 and updates the rationale comment now that Langfuse supports the required span shape. |
CHANGELOG.md |
Adds an Unreleased/Fixed entry documenting the Langfuse per-branch dispatch-span behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
The Langfuse observer now synthesizes a per-branch dispatch-span observation under a
parallel_branchesdispatcher node, so each branch's inner observations nest under their own branch span (a three-level dispatcher / per-branch-span / inner-nodes tree) instead of parenting directly under the dispatcher.Why
Observability §4.3 + §8.4.2 (proposal 0042) + proposal 0044 mandate the per-branch dispatch span. The OTel observer produces it (
parallel_branches_branch_spans); the Langfuse observer shipped without it (proposal 0044 was OTel-only in v0.11.0), so a parallel-branches Langfuse trace was a flat two-level tree. This closes that gap and un-defers conformance fixture 030.Changes
src/openarmature/observability/langfuse/observer.py: two_InvStatecaches (parallel_branches_branch_spans,parallel_branches_branch_names); cache the declared branch names on the pb-node started event; synthesize the per-branch observation (once per branch) in_sync_subgraph_observations; open/close helpers; per-branch parent resolution so inner branch nodes reparent under their branch span; andbranch_name+fan_out_indexon the Generation metadata. Renamed_find_fan_out_node_observationto_find_node_observation(it serves both the fan-out and pb node).tests/conformance/test_observability_langfuse.py: un-defer 030.tests/conformance/test_observability.py: move 030 to_LANGFUSE_HARNESS_FIXTURES.tests/unit/test_observability_langfuse.py: a two-node subgraph-branch test asserting exactly one per-branch observation (guards the synthesis idempotency).CHANGELOG.md: Unreleased/Fixed entry (rides into v0.16.0).Callable branches (proposal 0075) are unchanged: the single branch observation already is the per-branch Span observation (the branch is the single emitting unit, no inner nodes to nest), so the synthesis is gated to the subgraph-branch path and the existing callable-branch test stays green.
Test plan
tests/conformance/test_observability_langfuse.py(030 + the callable-branch test) +tests/unit/test_observability_langfuse.pypass.tests/-> 1464 passed. ruff + pyright clean.